import {Layer} from "./Layer";
import {FeatureCollection, MultiPolygon, Polygon} from "@turf/helpers";
import {
    BufferGeometry,
    Color,
    EdgesGeometry, Line, LineBasicMaterial, LineSegments, Mesh, MeshBasicMaterial, Shape, ShapeBufferGeometry,
    ShapeGeometry,
    Vector2, Vector3, Matrix4, Object3D
} from "three";
import {LatLng} from "../geom/LatLng";
import {Position} from "@turf/helpers/lib/geojson";
import {FillStyle, FillStyleConfig} from "./style/FillStyle";
import {Filter} from "./filter/Filter";

export class FillLayer extends Layer {

    protected fillMaterial: MeshBasicMaterial;

    protected outLineMaterial: LineBasicMaterial;

    public style: FillStyle;

    public filter: Filter;

    constructor(layerId: string, sourceId: string, filter?: Filter) {
        super(layerId, sourceId);

        this.style = new FillStyle();
        this.refreshStyle();
        this.filter = filter;
    }

    public setStyle(config: FillStyleConfig) {
        if (config.fillColor) {
            this.style.fillColor = new Color(config.fillColor as any).convertLinearToGamma();
        }
        if (config.fillOpacity) {
            this.style.fillOpacity = config.fillOpacity;
        }
        if (config.fillOutlineColor) {
            this.style.fillOutlineColor = new Color(config.fillOutlineColor as any).convertLinearToGamma();
        }
        if (config.fillOutlineWidth) {
            this.style.fillOutlineWidth = config.fillOutlineWidth;
        }
        this.refreshStyle();
    }

    protected refreshStyle() {
        this.fillMaterial = new MeshBasicMaterial({
            color: this.style.fillColor,
            opacity: this.style.fillOpacity,
            transparent: true
        });
        this.outLineMaterial = new LineBasicMaterial({
            color: this.style.fillOutlineColor,
            linewidth: this.style.fillOutlineWidth,
            transparent: true
        });
    }

    onSourceDataChange(features: FeatureCollection): void {
        let polygonArray: Array<ShapeGeometry> = [];

        features.features
            .filter(feature => {
                return this.filter ? this.filter.filter(feature) : true;
            })
            .forEach(feature => {
                if (feature.geometry.type == "Polygon") {
                    let polygon = feature.geometry as Polygon;

                    let polygonShape = this.coordinatesToShape(polygon.coordinates);

                    let geom = new ShapeGeometry(polygonShape);

                    polygonArray.push(geom);
                } else if (feature.geometry.type == "MultiPolygon") {
                    let multiPolygon = feature.geometry as MultiPolygon;

                    let shapes: Array<Shape> = [];

                    multiPolygon.coordinates.forEach(coordinates => {
                        let polygonShape = this.coordinatesToShape(coordinates);

                        shapes.push(this.coordinatesToShape(coordinates))
                    });

                    polygonArray.push(new ShapeGeometry(shapes));
                }
            });

        let meshArray: Array<Object3D> = [];

        let rootPolygon = polygonArray[0];

        for (let i = 1; i < polygonArray.length; i++) {
            polygonArray[i].translate(0, 0, Math.random() / 10);
            rootPolygon.merge(polygonArray[i]);
        }

        meshArray.push(new Mesh(new BufferGeometry().fromGeometry(rootPolygon), this.fillMaterial));

        meshArray.push(new LineSegments(new EdgesGeometry(rootPolygon
            , 1), this.outLineMaterial));

        this.scene.addLayer(this.layerId, meshArray);
    }

    private coordinatesToShape(coordinates: Position[][]): Shape {
        let points: Vector2[] = [];

        coordinates.forEach(coordinates => {
            coordinates.forEach(position => {
                let vec = this.map.mapRenderer.camera.projection(new LatLng(position[1], position[0]));

                points.push(vec)
            })
        });

        return new Shape(points);
    }
}
